`include "ysyx_23060189_cpu.svh"
`include "ysyx_23060189_isa.svh"

module ysyx_23060189_CSR(
  input  wire                             clk,
  input  wire                             rst,
  input  wire [`ysyx_23060189_DataBus]    inst,
  input  wire [`ysyx_23060189_AddrBus]    pc,
  input  wire [`ysyx_23060189_CsrTypeBus] csr_cmd,
  input  wire [`ysyx_23060189_DataBus]    csr_data,
  output wire [`ysyx_23060189_DataBus]    csr_out
);
  // wire
  wire [`ysyx_23060189_DataBus] data;
  wire [`ysyx_23060189_DataBus] out;

  wire [`ysyx_23060189_AddrBus] mtvec_in;
  wire [`ysyx_23060189_AddrBus] mtvec_out;
  reg  mtvec_wen;

  wire [`ysyx_23060189_DataBus] mstatus_in;
  wire [`ysyx_23060189_DataBus] mstatus_out;
  reg  mstatus_wen;

  wire [`ysyx_23060189_AddrBus] mepc_in;
  wire [`ysyx_23060189_AddrBus] mepc_out;
  reg  mepc_wen;

  wire [`ysyx_23060189_DataBus] mcause_in;
  wire [`ysyx_23060189_DataBus] mcause_out;
  reg  mcause_wen;

  wire [`ysyx_23060189_DataBus] mvendorid_out;

  wire [`ysyx_23060189_DataBus] marchid_out;

  wire [11:0] csr_addr;

  assign csr_addr   = inst[31:20];
  assign mtvec_in   = data;
  assign mstatus_in = data;

  Reg #(`ysyx_23060189_ADDR_W, 32'h0) mtvec (
    .clk(clk),
    .rst(rst),
    .din(mtvec_in),
    .dout(mtvec_out),
    .wen(mtvec_wen)
  );
  Reg #(`ysyx_23060189_DATA_W, 32'h1800) mstatus (
    .clk(clk),
    .rst(rst),
    .din(mstatus_in),
    .dout(mstatus_out),
    .wen(mstatus_wen)
  );
  Reg #(`ysyx_23060189_ADDR_W, 32'h0) mepc (
    .clk(clk),
    .rst(rst),
    .din(mepc_in),
    .dout(mepc_out),
    .wen(mepc_wen)
  );
  Reg #(`ysyx_23060189_DATA_W, 32'h0) mcause (
    .clk(clk),
    .rst(rst),
    .din(mcause_in),
    .dout(mcause_out),
    .wen(mcause_wen)
  );
  // read-only
  Reg #(`ysyx_23060189_DATA_W, 32'h79737978) mvendorid (
    .clk(clk),
    .rst(rst),
    .din(0),
    .dout(mvendorid_out),
    .wen(1'b0)
  );
  Reg #(`ysyx_23060189_DATA_W, 32'h15fdedd) marchid (
    .clk(clk),
    .rst(rst),
    .din(0),
    .dout(marchid_out),
    .wen(1'b0)
  );

  MuxKeyWithDefault #(4, 12, `ysyx_23060189_DATA_W) Mux_out(out, csr_addr, 32'b0, {
    12'h305, mtvec_out,
    12'h300, mstatus_out,
    12'h341, mepc_out,
    12'h342, mcause_out,
    12'hF11, mvendorid_out,
    12'hF12, marchid_out
  });

  MuxKey #(2, `ysyx_23060189_CSR_TYPE_W, `ysyx_23060189_DATA_W) Mux_csr_in(data, csr_cmd, {
    `ysyx_23060189_CSR_W, csr_data,
    `ysyx_23060189_CSR_S, csr_data | csr_out
  });

  MuxKeyWithDefault #(1, `ysyx_23060189_CSR_TYPE_W + `ysyx_23060189_DATA_W, `ysyx_23060189_DATA_W) Mux_mepc_in(mepc_in, {csr_cmd, inst}, data, {
    {`ysyx_23060189_CSR_P, `ysyx_23060189_ISA_ECALL}, pc
  });

  MuxKeyWithDefault #(1, `ysyx_23060189_CSR_TYPE_W + `ysyx_23060189_DATA_W, `ysyx_23060189_DATA_W) Mux_mcause_in(mcause_in, {csr_cmd, inst}, data, {
    {`ysyx_23060189_CSR_P, `ysyx_23060189_ISA_ECALL}, 32'd11
  });

  MuxKeyWithDefault #(2, `ysyx_23060189_CSR_TYPE_W + `ysyx_23060189_DATA_W, `ysyx_23060189_DATA_W) Mux_csr_out(csr_out, {csr_cmd, inst}, out, {
    {`ysyx_23060189_CSR_P, `ysyx_23060189_ISA_ECALL}, mtvec_out,
    {`ysyx_23060189_CSR_P, `ysyx_23060189_ISA_MRET},  mepc_out
  });

  always @(*) begin
    if (csr_cmd == `ysyx_23060189_CSR_P && inst == `ysyx_23060189_ISA_ECALL) begin
      mtvec_wen = 0;
      mstatus_wen = 0;
      mepc_wen = 1;
      mcause_wen = 1;
    end
    else if (csr_cmd == `ysyx_23060189_CSR_P && inst == `ysyx_23060189_ISA_MRET) begin
      mtvec_wen = 0;
      mstatus_wen = 0;
      mepc_wen = 0;
      mcause_wen = 0;
    end
    else if (csr_cmd == `ysyx_23060189_CSR_N) begin
      mtvec_wen = 0;
      mstatus_wen = 0;
      mepc_wen = 0;
      mcause_wen = 0;
    end
    else begin
      case(csr_addr)
        12'h305 : begin
          mtvec_wen = 1;
          mstatus_wen = 0;
          mepc_wen = 0;
          mcause_wen = 0;
        end

        12'h300 : begin
          mtvec_wen = 0;
          mstatus_wen = 1;
          mepc_wen = 0;
          mcause_wen = 0;
        end

        12'h341 : begin
          mtvec_wen = 0;
          mstatus_wen = 0;
          mepc_wen = 1;
          mcause_wen = 0;
        end

        12'h342 : begin
          mtvec_wen = 0;
          mstatus_wen = 0;
          mepc_wen = 0;
          mcause_wen = 1;
        end

        default: begin
          mtvec_wen = 0;
          mstatus_wen = 0;
          mepc_wen = 0;
          mcause_wen = 0;
        end
      endcase
    end
  end

  export "DPI-C" function fn_csr;

  function void fn_csr(output int mtvec, output int mret);
    // mtvec = {31'h0, mcause_wen};
    // mtvec = {29'h0, csr_cmd};
    if (csr_cmd == `ysyx_23060189_CSR_P && inst == `ysyx_23060189_ISA_MRET) begin
      mtvec = csr_out;
      mret = 1;
    end
    else begin
      mtvec = 0;
      mret = 0;
    end
  endfunction

endmodule
